﻿using Dijiang.Errors;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Dijiang.Values
{
    public class DNumber : DValue
    {
        public interface INum : IEquatable<INum>
        {
            bool IsZero();

            public static INum operator - (INum num)
            {
                switch (num)
                {
                    case IntNum i:
                        i.value = -i.value;
                        return i;

                    case FloatNum f:
                        f.value = -f.value;
                        return f;

                    default:
                        return new IntNum { value = 0 };
                }
            }
            public static INum operator + (INum lhs, INum rhs)
            {
                if (lhs is IntNum il)
                {
                    if (rhs is IntNum ir)
                    {
                        return il + ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return il + fr;
                    }
                }
                else if (lhs is FloatNum fl)
                {
                    if (rhs is IntNum ir)
                    {
                        return fl + ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return fl + fr;
                    }
                }

                return new IntNum { value = 0 };
            }
            public static INum operator - (INum lhs, INum rhs)
            {
                if (lhs is IntNum il)
                {
                    if (rhs is IntNum ir)
                    {
                        return il - ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return il - fr;
                    }
                }
                else if (lhs is FloatNum fl)
                {
                    if (rhs is IntNum ir)
                    {
                        return fl - ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return fl - fr;
                    }
                }

                return new IntNum { value = 0 };
            }
            public static INum operator * (INum lhs, INum rhs)
            {
                if (lhs is IntNum il)
                {
                    if (rhs is IntNum ir)
                    {
                        return il * ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return il * fr;
                    }
                }
                else if (lhs is FloatNum fl)
                {
                    if (rhs is IntNum ir)
                    {
                        return fl * ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return fl * fr;
                    }
                }

                return new IntNum { value = 0 };
            }
            public static INum operator / (INum lhs, INum rhs)
            {
                if (lhs is IntNum il)
                {
                    if (rhs is IntNum ir)
                    {
                        return il / ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return il / fr;
                    }
                }
                else if (lhs is FloatNum fl)
                {
                    if (rhs is IntNum ir)
                    {
                        return fl / ir;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return fl / fr;
                    }
                }

                return new IntNum { value = 0 };
            }
            public static bool operator > (INum lhs, INum rhs)
            {
                if (lhs is IntNum il)
                {
                    if (rhs is IntNum ir)
                    {
                        return il.value > ir.value;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return il.value > fr.value;
                    }
                }
                else if (lhs is FloatNum fl)
                {
                    if (rhs is IntNum ir)
                    {
                        return fl.value > ir.value;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return fl.value > fr.value;
                    }
                }

                return false;
            }
            public static bool operator < (INum lhs, INum rhs)
            {
                if (lhs is IntNum il)
                {
                    if (rhs is IntNum ir)
                    {
                        return il.value < ir.value;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return il.value < fr.value;
                    }
                }
                else if (lhs is FloatNum fl)
                {
                    if (rhs is IntNum ir)
                    {
                        return fl.value < ir.value;
                    }
                    else if (rhs is FloatNum fr)
                    {
                        return fl.value < fr.value;
                    }
                }

                return false;
            }

        }
        public struct IntNum : INum
        {
            public int value;

            public bool IsZero()
            {
                return value == 0;
            }

            public bool Equals(INum? other)
            {
                return other switch
                {
                    IntNum i => i.value == value,
                    FloatNum f => f.value == value,
                    _ => false,
                };
            }

            public static IntNum operator + (IntNum lhs, IntNum rhs)
            {
                return new IntNum { value = lhs.value + rhs.value };
            }

            public static FloatNum operator + (IntNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value + rhs.value };
            }

            public static IntNum operator - (IntNum lhs, IntNum rhs)
            {
                return new IntNum { value = lhs.value - rhs.value };
            }

            public static FloatNum operator - (IntNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value - rhs.value };
            }

            public static IntNum operator * (IntNum lhs, IntNum rhs)
            {
                return new IntNum { value = lhs.value * rhs.value };
            }

            public static FloatNum operator * (IntNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value * rhs.value };
            }

            public static IntNum operator / (IntNum lhs, IntNum rhs)
            {
                return new IntNum { value = lhs.value / rhs.value };
            }

            public static FloatNum operator / (IntNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value / rhs.value };
            }

        }
        public struct FloatNum : INum
        {
            public float value;

            public bool IsZero()
            {
                return value == 0f;
            }

            public bool Equals(INum? other)
            {
                return other switch
                {
                    IntNum i => i.value == value,
                    FloatNum f => f.value == value,
                    _ => false,
                };
            }

            public static FloatNum operator + (FloatNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value + rhs.value };
            }

            public static FloatNum operator + (FloatNum lhs, IntNum rhs)
            {
                return new FloatNum { value = lhs.value + rhs.value };
            }

            public static FloatNum operator - (FloatNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value - rhs.value };
            }

            public static FloatNum operator - (FloatNum lhs, IntNum rhs)
            {
                return new FloatNum { value = lhs.value - rhs.value };
            }

            public static FloatNum operator * (FloatNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value * rhs.value };
            }

            public static FloatNum operator * (FloatNum lhs, IntNum rhs)
            {
                return new FloatNum { value = lhs.value * rhs.value };
            }

            public static FloatNum operator / (FloatNum lhs, FloatNum rhs)
            {
                return new FloatNum { value = lhs.value / rhs.value };
            }

            public static FloatNum operator / (FloatNum lhs, IntNum rhs)
            {
                return new FloatNum { value = lhs.value / rhs.value };
            }
        }

        [NotNull]
        public INum num;

        public int IntVal
        {
            get => num switch
            {
                IntNum i => i.value,
                FloatNum f => (int)f.value,
                _ => 0,
            };
        }

        public float FloatVal
        {
            get => num switch
            {
                IntNum i => i.value,
                FloatNum f => f.value,
                _ => 0,
            };
        }

        public DNumber(int value)
        {
            num = new IntNum { value = value };
        }

        public DNumber(float value)
        {
            num = new FloatNum { value = value };
        }

        public DValue Eval(CmdEnv env)
        {
            return this;
        }

        public DValue Copy()
        {
            return num switch
            {
                IntNum iv => new DNumber(iv.value),
                FloatNum fv => new DNumber(fv.value),
                _ => throw new NotImplementedException(), // Never happens
            };
        }
        public override string ToString()
        {
            return num switch
            {
                IntNum iv => iv.value.ToString(),
                FloatNum fv => fv.value.ToString(),
                _ => throw new NotImplementedException(),
            };
        }

        public bool Equals(DValue? other)
        {
            return other is DNumber no && num.Equals(no.num);
        }
    }
}
